package cn.blu10ph.trustshare.util;

import com.google.common.io.BaseEncoding;
import org.bouncycastle.bcpg.HashAlgorithmTags;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openpgp.*;
import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
import org.bouncycastle.openpgp.operator.jcajce.*;
import org.springframework.util.ObjectUtils;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.Base64;
import java.util.Date;

/**
 * <p> PgpUtil </p >
 *
 * @author cxx
 * @date 2024/4/6 16:54
 */
public class PgpUtil {

    protected static void addBCSupport(){
        if (Security.getProvider("BC") == null) {
            Security.addProvider(new BouncyCastleProvider());
        }
    }

    public static PGPKeyPair createKeyPair(
            int rsaWidth, Date date
    ) throws NoSuchAlgorithmException, NoSuchProviderException, PGPException {
        addBCSupport();
        if(rsaWidth<=0){
            rsaWidth = 2048;
        }
        if(ObjectUtils.isEmpty(date)){
            date = new Date();
        }
        KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", "BC");
        kpg.initialize(rsaWidth);
        KeyPair keyPair = kpg.generateKeyPair();
        return new JcaPGPKeyPair(PGPPublicKey.RSA_GENERAL, keyPair, date);
    }

    public static PGPSecretKey createSecretKey(String id, String password, int rsaWidth, Date date) throws Exception {
        addBCSupport();
        char[] passPhrase = password.toCharArray();
        PGPKeyPair keyPair = PgpUtil.createKeyPair(rsaWidth, date);
        PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build().get(HashAlgorithmTags.SHA1);

        return new PGPSecretKey(
                PGPSignature.DEFAULT_CERTIFICATION,
                keyPair,
                id,
                sha1Calc,
                null,
                null,
                new JcaPGPContentSignerBuilder(keyPair.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA1),
                new JcePBESecretKeyEncryptorBuilder(PGPEncryptedData.CAST5, sha1Calc).setProvider("BC").build(passPhrase)
        );
    }

    public static String getSecretKeyEncode(PGPSecretKey secretKey) throws Exception {
        return Base64.getMimeEncoder().encodeToString(secretKey.getEncoded());
    }

    public static PGPSecretKey loadSecretKey(String privateKey) throws Exception {
        addBCSupport();
        byte[] privateKeyByte = Base64.getMimeDecoder().decode(privateKey);
        ByteArrayInputStream input = new ByteArrayInputStream(privateKeyByte);
        PGPSecretKeyRingCollection keyRingCollection = new PGPSecretKeyRingCollection(
                PGPUtil.getDecoderStream(input), new JcaKeyFingerprintCalculator());
        PGPSecretKeyRing keyRing = keyRingCollection.getKeyRings().next();
        return keyRing.getSecretKeys().next();
    }

    public static String getPublicKeyEncode(PGPPublicKey publicKey) throws Exception {
        return Base64.getMimeEncoder().encodeToString(publicKey.getEncoded());
    }

    public static String getPublicKeyFingerprint(PGPPublicKey publicKey) {
        byte[] fingerprintCreate = publicKey.getFingerprint();
        return BaseEncoding.base16().encode(fingerprintCreate).toUpperCase();
    }

    public static PGPPublicKey loadPublicKey(String publicKey) throws Exception {
        addBCSupport();
        byte[] publicKeyByte = Base64.getMimeDecoder().decode(publicKey);
        ByteArrayInputStream input = new ByteArrayInputStream(publicKeyByte);
        PGPPublicKeyRingCollection keyRingCollection = new PGPPublicKeyRingCollection(
                PGPUtil.getDecoderStream(input), new JcaKeyFingerprintCalculator());
        PGPPublicKeyRing keyRing = keyRingCollection.getKeyRings().next();
        return keyRing.getPublicKeys().next();
    }

    public static PGPPrivateKey getPrivateKey(PGPSecretKey pgpSecKey, String password) throws PGPException {
        return pgpSecKey.extractPrivateKey(
                new JcePBESecretKeyDecryptorBuilder()
                        .setProvider(new BouncyCastleProvider())
                        .build(password.toCharArray())
        );
    }

    public static PGPPublicKey getPublicKey(PGPSecretKey pgpSecKey) {
        return pgpSecKey.getPublicKey();
    }

    public static String createSignature(String content, PGPSecretKey pgpSecKey, String password) throws PGPException, IOException {
        PGPPrivateKey pgpPrivKey = getPrivateKey(pgpSecKey, password);
        PGPSignatureGenerator sGen = new PGPSignatureGenerator(
                new JcaPGPContentSignerBuilder(
                        pgpSecKey.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA1
                ).setProvider(new BouncyCastleProvider())
        );
        sGen.init(PGPSignature.BINARY_DOCUMENT, pgpPrivKey);
        sGen.update(content.getBytes(StandardCharsets.UTF_8));
        byte[] signByte = sGen.generate().getEncoded();
        return BaseEncoding.base16().encode(signByte).toUpperCase();
    }

    public static boolean verifySignature(String content, String sign, PGPPublicKey publicKey) throws IOException, PGPException {
        byte[] signed = BaseEncoding.base16().decode(sign);
        PGPObjectFactory pgpFact = new PGPObjectFactory(signed, new JcaKeyFingerprintCalculator());
        PGPSignatureList p3 = null;
        Object o = pgpFact.nextObject();
        if (o instanceof PGPCompressedData c1) {
            pgpFact = new PGPObjectFactory(c1.getDataStream(), new JcaKeyFingerprintCalculator());
            p3 = (PGPSignatureList) pgpFact.nextObject();
        } else {
            p3 = (PGPSignatureList) o;
        }
        PGPSignature sig = p3.get(0);
        sig.init(new JcaPGPContentVerifierBuilderProvider().setProvider(new BouncyCastleProvider()), publicKey);
        sig.update(content.getBytes(StandardCharsets.UTF_8));
        return sig.verify();
    }

    public static void main(String[] args) throws Exception {
        String id = "blu10ph";
        String password = "blu10ph!";

        System.out.println("=============== CREATE ==================");
        System.out.println();

        PGPSecretKey secretKeyCreate = PgpUtil.createSecretKey(id, password, 2048, null);
        String secretKeyCreateString = PgpUtil.getSecretKeyEncode(secretKeyCreate);
        System.out.println("secretKeyCreateString:");
        System.out.println(secretKeyCreateString);
        System.out.println();

        PGPPrivateKey privateKeyCreate = PgpUtil.getPrivateKey(secretKeyCreate, password);
        String privateKeyCreateString = Base64.getMimeEncoder().encodeToString(privateKeyCreate.getPrivateKeyDataPacket().getEncoded());
        System.out.println("privateKeyCreateString:");
        System.out.println(privateKeyCreateString);
        System.out.println();

        PGPPublicKey publicKeyCreate = secretKeyCreate.getPublicKey();
        String publicKeyCreateString = PgpUtil.getPublicKeyEncode(publicKeyCreate);
        System.out.println("publicKeyCreateString:");
        System.out.println(publicKeyCreateString);
        System.out.println();

        String publicKeyCreateFingerprint = PgpUtil.getPublicKeyFingerprint(publicKeyCreate);
        System.out.println("publicKeyCreateFingerprint:");
        System.out.println(publicKeyCreateFingerprint);
        System.out.println();

        System.out.println("=============== LOAD SECRET ==================");
        System.out.println();

        PGPSecretKey secretKey = PgpUtil.loadSecretKey(secretKeyCreateString);
        String secretKeyString = Base64.getMimeEncoder().encodeToString(secretKey.getEncoded());
        System.out.println("secretKeyLoadString:");
        System.out.println(secretKeyString);
        System.out.println();

        PGPPrivateKey privateKey = PgpUtil.getPrivateKey(secretKey, password);
        String privateKeyString = Base64.getMimeEncoder().encodeToString(privateKey.getPrivateKeyDataPacket().getEncoded());
        System.out.println("privateKeyString:");
        System.out.println(privateKeyString);
        System.out.println();

        PGPPublicKey publicKey = secretKey.getPublicKey();
        String publicKeyString = PgpUtil.getPublicKeyEncode(publicKey);
        System.out.println("publicKeyString:");
        System.out.println(publicKeyString);
        System.out.println();

        String publicKeyFingerprint = PgpUtil.getPublicKeyFingerprint(publicKey);
        System.out.println("publicKeyFingerprint:");
        System.out.println(publicKeyFingerprint);
        System.out.println();

        System.out.println("=============== LOAD PUBLIC ==================");
        System.out.println();

        PGPPublicKey publicKeyLoad = PgpUtil.loadPublicKey(publicKeyCreateString);
        String publicKeyLoadString = PgpUtil.getPublicKeyEncode(publicKeyLoad);
        System.out.println("publicKeyLoadString:");
        System.out.println(publicKeyLoadString);
        System.out.println();

        String publicKeyLoadFingerprint = PgpUtil.getPublicKeyFingerprint(publicKeyLoad);
        System.out.println("publicKeyLoadFingerprint:");
        System.out.println(publicKeyLoadFingerprint);
        System.out.println();

        System.out.println("=============== SIGNATURE ==================");
        System.out.println();

        String content = "blu10ph content";
        String sign = PgpUtil.createSignature(content, secretKey, password);
        System.out.println("sign:");
        System.out.println(sign);
        System.out.println();

        boolean flg = PgpUtil.verifySignature(content, sign, publicKey);
        System.out.println("verify flg:");
        System.out.println(flg);
        System.out.println();

        System.out.println("=============== END ==================");
        System.out.println();

    }
}
